package com.benshou.bcss.common.lock;

import lombok.NonNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * 包装器：用于确保 {@link Lock} 正确执行 {@code lock()}
 * <p>
 *     在原生调用 {@link Lock} 的 {@code lock()} 方法时，有可能会因为虚拟机自身的原因意外提前返回。
 *     此时，有可能仍未获取到锁占用。因此，使用此包装起用以确保 {@code lock()} 到指定的时间后再返回。
 * </p>
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
public record EnsureLockWrapper<T extends Lock>(
        @NonNull T lock) {

    /**
     * 调用 {@link Lock} 的 {@code lock()}。（若未获得锁占用）此方法可以确保 {@code lock()} 到指定的时间后再返回。
     *
     * @param timeout 超时时间
     * @param unit 超时时间的单位
     * @param locking 具体的 {@code lock()} 方法
     * @return 是否已获得锁占用
     * @throws InterruptedException 若被外部中断，抛出此异常
     */
    public boolean tryLock(
            long timeout,
            @NonNull TimeUnit unit,
            @NonNull TryLocking<? super T> locking) throws InterruptedException {
        var start = System.nanoTime();
        var timeoutNanos = unit.toNanos(timeout);

        boolean locked;
        // 注意：nanos 可能溢出（超过 273 年），必须使用减法运算
        var remainNanos = timeoutNanos - (System.nanoTime() - start);
        do {
            locked = locking.tryLock(lock, remainNanos, TimeUnit.NANOSECONDS);
        } while (!locked && (remainNanos = timeoutNanos - (System.nanoTime() - start)) > 0);

        return locked;
    }

    /**
     * 调用 {@link Lock} 的 {@code lock()}。（若未获得锁占用）此方法可以确保 {@code lock()} 到指定的时间后再返回。
     *
     * @param timeout 超时时间
     * @param unit 超时时间的单位
     * @return 是否已获得锁占用
     * @throws InterruptedException 若被外部中断，抛出此异常
     * @see EnsureLockWrapper#tryLock(long, TimeUnit, TryLocking)
     * @see Lock#tryLock(long, TimeUnit)
     */
    public boolean tryLock(long timeout, @NonNull TimeUnit unit) throws InterruptedException {
        return tryLock(timeout, unit, Lock::tryLock);
    }
}
